#include "v8-function-callback.h"
#include "v8-function.h"
#include "v8.h"
#include "v8-initialization.h"
#include "libplatform/libplatform.h"
#include "module.hpp"
#include "class.hpp"
#include "basic.h"

using namespace v8;
using namespace v8pp;
auto default_allocator_ = v8::ArrayBuffer::Allocator::NewDefaultAllocator();
auto defaultPlatform_ = v8::platform::NewDefaultPlatform();

v8::Isolate *InitV8() {
    v8::V8::SetFlagsFromString("--expose-gc");
    v8::V8::InitializePlatform(defaultPlatform_.get());
    v8::V8::Initialize();
    auto para = v8::Isolate::CreateParams();
    para.array_buffer_allocator = default_allocator_;
    auto iso = v8::Isolate::New(para);
    v8::Locker locker(iso);
    v8::Isolate::Scope isolate_scope(iso);
    v8::HandleScope handle_scope(iso);

//    auto refPtr = new v8::Persistent<v8::Context>();
//    refPtr->Reset(iso, context);
//    iso->SetData(0, refPtr);
    return iso;
}

int test(int a, int b) {
    return a + b;
}

class TestClass {
public:

    TestClass(int b1) {
    }

    int Test2(int b) {
        return 0;
    }

    int Test2() {
        return 0;
    }

    virtual void Release() {
        delete this;
    }

    static bool Match(const TestClass &other) {
        return false;
    }

protected:
    ~TestClass() = default;


};

class TestClassImpl : public TestClass {
public:
    int a[4];

    TestClassImpl(int b1) : TestClass(b1) {
    }


};


TestClass *CreateTestClass(int b) {
    return new TestClassImpl(b);
}

int test2(void *a) {
    return 0;
}

int test2() {
    return 0;
}

void test3(int a[2]) {
    a[1] = 5;
}

int *TestIntPtr(int *x) {
    *x = 5;
    return x;
}

void *MyAllocate(size_t n) {
    return malloc(n);
}

void MyFree(void *ptr) {
    free(ptr);
}

void MyLog(int a) {
    printf("%d\n", a);
}




void FreeBasic(BasicWrapper<int> *t) {

}

int main() {

    auto iso = InitV8();
//    v8::Persistent<v8::Context> *contextRefPtr;
//    auto cFunctionName = "v8_wrap_test2";
//    {
    v8::Locker locker(iso);
    v8::Isolate::Scope isolate_scope(iso);
    v8::HandleScope handle_scope(iso);

//        auto slotData = iso->GetData(0);
//        contextRefPtr = reinterpret_cast<v8::Persistent<v8::Context> *>(slotData);
//
//    }

    auto objectTemplate = v8::ObjectTemplate::New(iso);
    auto const &context = v8::Context::New(iso, nullptr, objectTemplate);
    v8::Context::Scope context_scope(context);


    std::function<void(v8::Isolate *iso, TestClass *const &data)> dtor;
    dtor = [](v8::Isolate *iso, TestClass *const &data) -> void {
        data->Release();
        iso->AdjustAmountOfExternalAllocatedMemory(-static_cast<int64_t>(raw_ptr_traits::object_size(data)));
    };
    v8pp::class_<TestClass> test(iso, dtor);
    std::function<TestClass*(int)> ff;
    test.ctor(CreateTestClass);
    test.function("Match", &TestClass::Match);
    test.function<int, int>("Test2", &TestClass::Test2);
    test.auto_wrap_objects(true);

    v8pp::class_<TestClassImpl> test_impl(iso, dtor);
    test_impl.var("a", &TestClassImpl::a);
    test_impl.auto_wrap_objects(true);
    test_impl.inherit<TestClass>();

    v8pp::class_<BasicWrapper<int>> BasicWrapperInt(iso, FreeBasic);
    BasicWrapperInt.ctor<>();
    BasicWrapperInt.function("getPtr", &BasicWrapper<int>::getPtr);
    BasicWrapperInt.function("getValue", &BasicWrapper<int>::getValue);
    BasicWrapperInt.function("setPtr", &BasicWrapper<int>::setPtr);
    BasicWrapperInt.function("setValue", &BasicWrapper<int>::setValue);
    BasicWrapperInt.auto_wrap_objects(true);

    v8pp::class_<BasicArrayWrapper<int>> BasicArrayWrapperInt(iso);
    BasicArrayWrapperInt.ctor<size_t>();
    BasicArrayWrapperInt.function("get", &BasicArrayWrapper<int>::get);
    BasicArrayWrapperInt.function("set", &BasicArrayWrapper<int>::set);
    BasicArrayWrapperInt.auto_wrap_objects(true);


    auto module = new v8pp::module(iso);
    module->function("MyAllocate", &MyAllocate);
    module->function("MyFree", &MyFree);
    module->class_("BasicWrapperInt", BasicWrapperInt);
    module->class_("BasicArrayWrapperInt", BasicArrayWrapperInt);
    module->class_("TestClass", test);
    module->class_("TestClassImpl", test_impl);
    module->function("MyLog", &MyLog);
    module->function("TestIntPtr", &TestIntPtr);
    module->function<int, void *>("test2", &test2);
    module->function("test3", &test3);


    auto obj = test.create_object(iso, 0);
    test.destroy_object(iso, convert<TestClass *>::from_v8(iso, obj));

    iso->RequestGarbageCollectionForTesting(v8::Isolate::GarbageCollectionType::kFullGarbageCollection);


    const Local<v8::Object> &mylib = module->new_instance();
    auto setlModuleResult=context->Global()->Set(context, v8::String::NewFromUtf8(iso, "mylib").ToLocalChecked(), mylib);
    if(!setlModuleResult.ToChecked()){
        return 0;
    }
    const char *origin = "test.js";
    const char *source = R"raw(
//        let ptr=mylib.MyAllocate(100);
//        mylib.MyFree(ptr);
//            let k=mylib.CreateTestClass(1);
//          k.Test2(0 );
//         let g= new mylib.TestClassImpl(100);
//        let vx=new mylib.BasicArrayWrapperInt(4);
//        vx.set(0,1);
//        vx.set(1,2);
//        vx.set(2,3);
//        vx.set(3,4);
//        g.a=vx;
//        let mkk=g.a;
       let x=new mylib.TestClass(1);
//        let m=new mylib.BasicWrapperInt();
//       mylib.TestIntPtr(m);
//        m.get();
)raw";
    MaybeLocal<String> maybeSrc =
            String::NewFromUtf8(iso, source, NewStringType::kNormal);
    MaybeLocal<String> maybeOgn =
            String::NewFromUtf8(iso, origin, NewStringType::kNormal);
    Local<String> src, ogn;
    if (!maybeSrc.ToLocal(&src) || !maybeOgn.ToLocal(&ogn)) {
        printf("failed \n");
        return 1;
    }

    ScriptOrigin script_origin(iso, ogn);
    Local<Script> script;
    if (!Script::Compile(context, src, &script_origin).ToLocal(&script)) {

        printf("failed \n");
        return 1;

    }
    Local<Value> result;
    if (!script->Run(context).ToLocal(&result)) {
        printf("failed \n");
        return 1;

    }
    iso->RequestGarbageCollectionForTesting(v8::Isolate::GarbageCollectionType::kFullGarbageCollection);
    return 0;
}

